#include "lsv_rcache.h"
#include "lsv_log.h"
#include "lsv_volume.h"
#include "volume_proto.h"
#include "list.h"

int lsv_rcache_cu_malloc_init(lsv_volume_proto_t *volume_proto, lsv_rcache_chunk_unit **cu){
	lsv_rcache_chunk_unit *tcu;
	lsv_s32_t err = 0;
        lsv_rcache_t *rcache = (lsv_rcache_t *)volume_proto->rcache;

        err = ymalloc((void **)&tcu, sizeof(lsv_rcache_chunk_unit));
        if(unlikely(err)){
        	err = -EINVAL;
	        DERROR("malloc for lsv_rcache_chunk_unit failed\n");
	        return err;
        }
	
        memset(tcu, 0, sizeof(lsv_rcache_chunk_unit));
        rcache->chunk_num ++;
        INIT_LIST_HEAD(&tcu->chunk_lru_hook);
        INIT_LIST_HEAD(&tcu->index_list);
        lsv_rwlock_init(&tcu->rwlock);
	
        *cu = tcu;
        return err;
}

int lsv_rcache_chunk_swap(lsv_volume_proto_t * volume_proto, lsv_rcache_chunk_unit *cu){
	lsv_s32_t page_hit_cnt = 0;
        lsv_s32_t i = 0;
        lsv_s32_t err = 0;
        lsv_rcache_t *rcache = (lsv_rcache_t *)volume_proto->rcache;

        // 多少页被访问
        for(i = 0; i < 4; i++){
                // 1的个数
                for(;cu->bmap[i]; ++page_hit_cnt){
                        cu->bmap[i] &= (cu->bmap[i] - 1);
                }
        }

        //判断是否swap out到ssd中，用于ssd读cache
        //过于随机的不要，过于顺序的不要
        if(cu->hit_count > 50 && page_hit_cnt > 25 && page_hit_cnt < 200){
                DINFO("swap out info: cu->hit_count: %d, page_hit_cnt: %d\n", cu->hit_count, page_hit_cnt);
                err = lsv_rcache_l2_cache_swap(volume_proto, cu);
                if(err < 0){
                        DERROR("call lsv_rcache_l2_cache_swap failed, err: %d\n", err);
                        return err;
                }
                rcache->l2_swap ++;
        }
        return err;
}

int lsv_rcache_cu_reset(lsv_volume_proto_t *volume_proto, lsv_rcache_chunk_unit *cu){
        (void) volume_proto;
        cu->chunk_id = 0;
        //cu->invalid_chunk_id = 0;
        cu->hit_count = 0;
        cu->bmap[0] = 0;
        cu->bmap[1] = 0;
        cu->bmap[2] = 0;
        cu->bmap[3] = 0;
        return 0;
}

int lsv_rcache_load_chunk(lsv_volume_proto_t *volume_proto, lsv_u32_t chunk_id,
                lsv_s32_t chunk_offset,lsv_u64_t off, lsv_s32_t size, buffer_t *append_buf){
        lsv_s32_t err = 0;
        lsv_s32_t page_offset = off % LSV_PAGE_SIZE;
        lsv_rcache_t *rcache = (lsv_rcache_t *)volume_proto->rcache;
        lsv_rcache_chunk_unit * cu = NULL;
        lsv_rcache_chunk_unit * tcu = NULL;
        struct list_head *pos, *n;

        DINFO("off %llu chunk_id %u chunk_off %d size %d\n", (LLU)off, chunk_id, chunk_offset, size);

        YASSERT(LSV_CHUNK_NULL != chunk_id);

        //避免load多个chunk，不在检查page 和 l2_cache的索引项
        err = lsv_rcache_chunk_hash_index_lookup(volume_proto, chunk_id, chunk_offset, off, size, append_buf);
        if(err == size){
                volume_proto->read_rc_hit++;
		rcache->chunk_hit_cnt ++;
                return err;
        }

        volume_proto->read_rc_miss++;

        //TODO 如果LSV_RCACHE_CHUNK_MAX > 0,即相当于在内存中分配一定内存用于缓存读出来的chunk，那么ssd中的chunk空间，用于缓存内存中换出的chunk即可
        if(LSV_RCACHE_CHUNK_NUM_MAX > 0){
            	if(rcache->chunk_num < LSV_RCACHE_CHUNK_NUM_MAX){
            		err = lsv_rcache_cu_malloc_init(volume_proto, &cu);
            		if(unlikely(err)){
                                err = -EINVAL;
                                DERROR("malloc for lsv_rcache_chunk_unit failed\n");
                                return err;
            		}
            		cu->status = LSV_RCACHE_LOADING;
                        DINFO("LSV_RCACHE chunk_num %d\n", rcache->chunk_num);
                } else {
                	while(1){
                		list_for_each_prev_safe(pos, n, &rcache->chunk_lru){
                			tcu = list_entry(pos, lsv_rcache_chunk_unit, chunk_lru_hook);
                			if(tcu->status != LSV_RCACHE_LOADING){
                				cu = tcu;
                				break;
                			}
                		}
                		if(NULL != cu){
                				break;
                		}
                		co_cond_wait(&rcache->chunk_id_cond);
                	}

                	cu->status = LSV_RCACHE_LOADING;

                	if(!list_empty(&cu->chunk_lru_hook))
                		list_del_init(&cu->chunk_lru_hook);
                	if(!list_empty(&cu->index_list))
                		list_del_init(&cu->index_list);

                        /*当LSV_RCACHE_CHUNK_NUM_MAX_L2>0时，即存在SSD_CACHE，在内存chunk换出时，换出到ssd cache中*/
                	if(LSV_RCACHE_CHUNK_NUM_MAX_L2 > 0){
                		err = lsv_rcache_chunk_swap(volume_proto, cu);
                		if(err < 0){
                                	DERROR("call lsv_rcache_chunk_swap failed, err: %d\n", err);
                                        return err;
                                }
                        }
                }

                rcache->chunk_load ++;
                lsv_rcache_cu_reset(volume_proto, cu);

                DINFO("request page not in readcache, call lsv_log_slog_read\n");

                err = lsv_log_read(volume_proto, chunk_id, cu->buf);
                if(err){
                        DERROR("call lsv_log_slog_read read chunk_id[%d] error:%d\n", chunk_id, err);
                        list_add(&cu->chunk_lru_hook, &rcache->chunk_lru);
                        return err;
                }
		
                cu->status = LSV_RCACHE_IN_USE;
                cu->chunk_id = chunk_id;
                list_add(&cu->chunk_lru_hook, &rcache->chunk_lru);

                //TODO 在load过程中，是否存在regc过程
                
                lsv_rcache_chunk_hash_index_insert(volume_proto, cu);

                // 该页加入page cache
                mbuffer_appendmem(append_buf, cu->buf + chunk_offset + page_offset, size);

                lsv_rcache_pu_insert(volume_proto, chunk_id, chunk_offset, off, size, cu->buf);

                DINFO("read chunk_id: %u return size: %d\n", chunk_id, size);

                co_cond_broadcast(&rcache->chunk_id_cond, 0);
                return size;
        } else {
                /*------------------------------------------ssd cache, LSV_RCACHE_CHUNK_NUM_MAX_L2 > 0 ---------------------------*/
        		//此处已在外围做判断，故此处不会被调用
                err = lsv_rcache_lookup_l2_cache(volume_proto,
                                chunk_id,
                                chunk_offset,
                                off,
                                size,
                                append_buf);
                if(err < 0){
                        DERROR("lsv_rcache_lookup_l2_cache exec failed, err: %d\n", err);
                        return err;
                }
        }

        return err;
}
